【C++ • STL】探究string的源码 您所在的位置:网站首页 std::string find_first_of源码 【C++ • STL】探究string的源码

【C++ • STL】探究string的源码

2024-07-11 23:49| 来源: 网络整理| 查看: 265

文章目录 一、深浅拷贝二、传统版写法的string类(简单)三、string类的模拟实现四、现代版写法的string类五、总结

ヾ(๑╹◡╹)ノ" 人总要为过去的懒惰而付出代价ヾ(๑╹◡╹)ノ"

一、深浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。 浅拷贝:(1)析构两次,造成程序崩溃(2)一个对象修改影响另外一个

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

编译器默认生成的拷贝构造,是浅拷贝,会是两个对象指向同一块空间,当程序结束的时候,那么两个对象都会进行销毁,那么一块空间就会进行多次释放,从而引起崩溃。

深拷贝:给每一个对象分配资源,保证多个对象之间不会因为共享资源而导致多次释放造成程序崩溃。

二、传统版写法的string类(简单) #pragma once #include using namespace std; #include namespace yyqx//为了与库里面的string进行区分 { //仅仅实现一个简单的string,仅仅考虑资源管理深浅拷贝问题 class string { public: //构造函数 string(const char* str) :_str(new char[strlen(str) + 1])//这里的+1,是为了'\0'开辟空间 { strcpy(_str, str);//拷贝的时候'\0'也拷贝了 } //拷贝构造(深拷贝) //s2(s1) string(const string& s) :_str(new char[strlen(s._str) + 1]) { strcpy(_str, s._str); } //赋值,也会有深浅拷贝的问题 string& operator=(const string& s) { if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值 { //delete[] _str;//首先进行释放 //_str = new char[strlen(s._str) + 1];//C++的new是不需要检查是否开辟空间 会抛异常 //strcpy(_str, s._str); //为了避免开辟空间失败,而本来的空间也被我们释放,可以先开启空间, //进行拷贝,然后再释放 char* tmp = new char[strlen(s._str) + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; } return *this; } //析构函数 ~string() { if (_str) { delete[] _str; } } //目的为了输出字符串 const char* c_str() const { return _str; }//返回c格式的字符串 //重载[] char& operator[](size_t pos) { assert(pos class string { public:

构造函数+析构函数: 写法1:

//构造函数 string(const char* str) :_size(strlen(str)) ,_capacity(_size) { _str = new char[strlen(str) + 1];//这里的+1,是为了'\0'开辟空间 strcpy(_str, str);//拷贝的时候'\0'也拷贝了 } string()//注意,这里不是给的空,而是给了一个空的字符串//标准库里的就是给了一个"" :_size(0) ,_capacity(0) { _str = new char[1]; _str[0] = '\0'; } 构造函数:初始化列表,初始化的顺序并不是初始化列表的顺序,而是成员变量在类中的声明次序。构造函数:注意默认的构造函数【编译器自动生成、缺省、函数重载】,默认的构造函数这里选择写一个同名函数,注意这里并不是给一个空指针,而是给了一个空字符串。 写法2:(最优写法) string(const char* str = "")//这里默认值不能给nullptr,strlen以及拷贝strcpy会崩溃 :_size(strlen(str)) ,_capacity(_size) { _str = new char[strlen(str) + 1];//这里的+1,是为了'\0'开辟空间 strcpy(_str, str);//拷贝的时候'\0'也拷贝了 } //析构函数 ~string() { if (_str) { delete[] _str; _str = nullptr;//好习惯 _size = 0; _capacity = 0; } } 缺省值这不能给nullptr,strlen以及拷贝strcpy时程序会崩溃注意初始化列表strcpy注意,拷贝的时候’\0’也拷贝了new开空间的时候,一定要多开一个给’\0’

拷贝构造+赋值重载函数+其他:

//拷贝构造(深拷贝) //s2(s1) string(const string& s) :_size(strlen(s._str)) ,_capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, s._str); } //赋值,也会有深浅拷贝的问题 string& operator=(const string& s) { if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值 { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; } //目的为了输出字符串 const char* c_str() const { return _str; }//返回c格式的字符串 char& operator[](size_t pos)//这里仅仅可以传入对象,不能传入const对象,如果是const对象,就会报错 { assert(pos return _size; } size_t capacity() const//写const,普通对象以及const对象都可以调用 { return _capacity; }

添加:

string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } void reverse(size_t n)//一个扩容的作用 { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str;//注意这里的释放不是free _str = tmp; _capacity = n; } } void resize(size_t n, char ch = '\0') { if (n if (n > _capacity) { reverse(n); } for (size_t i = _size; i if (_size == _capacity) { reverse(_capacity == 0 ? 4 : _capacity * 2);//如果是一个空字符串,就会导致并没有扩容, //扩容要注意刚开始没有容量的情况下 } _str[_size] = ch; _size++; _str[_size] = '\0';//注意\0,容易遗漏 } //append插入的字符个数是未知的,扩容二倍也不一定足够 void append(const char* str) { size_t len = _size + strlen(str); if (len > _capacity) { reverse(len); } strcpy(_str + _size, str); _size = len; }//但是我们一般用+= 判断容量是否满,如果 _size= _ capacity,容量扩2倍,new一个新容量的空间,释放旧空间,最后指针指向新的空间。append (append插入的字符个数是未知的,扩容二倍也不一定足够:解决办法:reverse预留空间【一个扩容的作用】)reverse 为string预留空间,避免多次扩容(提高效率)resize用处:扩空间+初始化;删除数据保留前n个 插入: string& insert(size_t pos, char ch) { assert(pos _str[end] = _str[end - 1]; --end; } _str[pos] = ch; _size++; return *this; } //插入\0,用c_str(遇到\0停止打印)打印显示在屏幕的字符串长度会减小或者不变,但是_size会变大 //用范围for或者迭代器可以打印出来 string& insert(size_t pos, const char* str) { assert(pos _capacity) { reverse(_size + len); } size_t end = _size + len; while (end > pos + len - 1)//这里注意 { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len);//防止为了遇见\0就不拷贝了(strcpy遇见\0就不拷贝了) _size += len; return *this; }

插入字符:

不可以用strcpy,在字符进行向后移的时候,不可以是同一块地址,对导致内容不是我们想要的,最后一个未知的字符移到_size然后就是倒数第二位移动,从后向前移动end=_size,当头插的时候,进入循环end会变成-1,因为是size_t,又是大于0所以又会进入循环,导致代码错误

插入字符串:

防止为了遇见\0就不拷贝了,所以用的是strncpy(strcpy遇见\0就不拷贝了)

删除:

//删除 string& erase(size_t pos, size_t len = npos) { assert(pos = npos) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin for (; pos return pos; } } return npos; } size_t find(const char* str, size_t pos = 0) { const char* p = strstr(_str + pos, str); if (p == nullptr) { return npos; } else { return p - _str; } } void clear() { _str[0] = '\0'; _size = 0; } private: char* _str; size_t _size;//有效字符的个数 size_t _capacity;//存储有效字符的空间大小 const static size_t npos;//正确的写法是在类外进行初始化 //const static size_t npos = -1;//这种写法也可以,但是违背了正确的写法,要注意 }; const size_t string::npos = -1; strstr返回的是指针,没有找到返回空指针。

流插入和流提取:

/流插入和流提取 //在类外 //不可以用c_str(),因为遇见\0会停止 //'\0'是不可以见字符,不会显示 //流插入 ostream& operator out (istream& in, string& s) { s.clear(); //要把对象里面的字符清理掉,否则当对象不是空的时候,会导致字符直接加到已有对象的后面。 //但是我们想要的是,对象是我们输入的字符串 //第一种思路(缺点:频繁的+=,字符串过大,会导致频发的扩容,影响效率) /*char ch; ch = in.get(); if (ch != ' ' && ch != '\n') { s += ch; ch = in.get(); } return in*/ //第二种思路(这种思路比较优,无论大小都可以避免频繁扩容) char ch; ch = in.get(); char buff[128] = { '\0' }; size_t i = 0; if (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 127) { s += buff; memset(buff, '\0', 128); i = 0; } ch = in.get(); } s += buff; return in; } '\0’是不可以见字符,不会显示clear()要把对象里面的字符清理掉,否则当对象不是空的时候,会导致字符直接加到已有对象的后面。但是我们想要的是,对象是我们输入的字符串

运算符重载:

//运算符重载 //比较大小 //全局函数.可以类比日期类 bool operator return strcmp(s1.c_str(), s2.c_str()) == 0; } bool operator return !(s1 =(const string& s1, const string& s2) { return s1 > s2 || s1 == s2; } bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); } }//这个是yyqx的大括号

这里是在全局变量,没有在类里面,是在类外

迭代器: string类private里面:

public: //迭代器 typedef char* iterator; typedef const char* const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } iterator begin() { return _str; } iterator end() { return _str + _size; } 四、现代版写法的string类

拷贝构造和赋值的现代写法

//拷贝构造(深拷贝) //s2(s1)//现代写法,剥削行为,要完成深拷贝, void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0)//这里要进行初始化,否则交换后,局部变量的销毁(随机值销毁,不可以) { //构造一个对象tmp,tmp里所有的东西this想要。this和tmp string tmp(s._str);//局部变量,出了作用域会销毁 swap(tmp); //tmp出了作用域会销毁 } //赋值,也会有深浅拷贝的问题 //现代写法 //第一种 //string& operator=(const string& s) //{ // if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值 // { // string tmp(s._str); // swap(tmp);//把tmp给this,出了作用域把this给tmp的值进行销毁 // } // return *this; //} //第二种 string& operator=(string s)//传值传参,拷贝构造,拷贝的值给this,并不会导致s的实参发生变化 { swap(s); return *this; } //掌握现代写法

补充知识点 遍历方式中有一个是范围for(范围for的底层实现是迭代器,如果没有迭代器的程序,代码会进行报错)

代码展示:

yyqx::string s("hello 12345"); for (auto ch : s) { cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有